package com.zwh.se.io;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * @author zwh
 * 通过代码演示什么是解码和编码，以及解释为什么会有乱码的现象出现
 * 此处的案例实际上就是使用tomcat服务器时会出现的解码问题
 *
 * 名词解释：
 * 什么是编码：编码是信息从一种形式或格式转换为另一种形式的过程。
 * 什么是解码：是编码的逆过程。
 * （以上简单理解：众所周知,计算机只认识01,但是我们用的文字可不是01,因此为了能够让计算机存储的某种01段能够代表现实世界中的某个字,
 *      因此诞生了"编码"和"解码"两个概念。比如说现在有个单词"测试",为了存到计算机,我将'测'当作21979,将'试'当作35797存入到计算机当中(这就是编码),
 *      这样,只要我能够按照这个已经确定的规则,从而从计算机取出21979和35797时,就认为我取出了"测试"这两个字(这就是解码)。
 *      上面的例子中,可认为我记录了一个映射表,里面存了各种字符与二进制之间的映射规则,而这种东西就有个学名称为"编码表"。
 *      而在现实中，由权威机构形成的"编码表"我们就可以将其称作"字符集"。）
 *
 *  以下是比较常见的一些字符集：
 *  ASCII       英文字符集     用一个字节的7位表示一个字符(首位是 0)
 *  IOS8859-1   西欧字符集     用一个字节的8位表示一个字符(为了兼容ASCII所做的取舍,首位是0就是ASCII的字符,首位是1就是西欧自己的字符)
 *  GB2312      简体中文字符集  最多使用两个字节编码表示一个字符(因为中文太多了)(且GB2312也兼容了ASCII中的字符)
 *  GBK         GB2312的升级，加入了繁体字    最多使用两个字节编码表示一个字符
 *  (扩展:怎么做到有时能一个字节表示一个字符,有时又能两个字节表示一个字符呢?)(举例:比如设立规则:如果首位是0则一字节一字符,首位是1则二字节一字符)
 *  Unicode     国际通用字符集，融合了目前人类使用的所有字符。为每个字符分配唯一的字符码(存储的编号就是'\u0000'-'\uFFFF'）
 *  (有趣的是,Unicode本身只规定了每个字符的数字编号是多少,却并没有规定这个编号如何存储。
 *      这是因为字符太多了,如果实现方式不够好(比如规定所有字符都以三个字节或四个字节存储),那么对于ASCII而言,就会有两个以上的字节存的都是0,
 *      会大大浪费存储空间,所以一直没能推广。直至UTF(UCS Transfer Format)编码规则的诞生。")
 *  UTF-8    以1~6个字节来表示一个字符
 *  UTF-16
 *  UTF-32
 *  (鉴于我自己对字符集的研究也不是说深入到那种程度的地步,所以到这里就先结束了)
 */
public class DecodeAndEncodeDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("----------测试由于字符集的不同而导致的乱码问题----------");
        String input = "测试";//模拟用户输入的中文数据
        //编码操作：String -> byte[]
        byte[] data = input.getBytes(StandardCharsets.UTF_8);
        System.out.println(Arrays.toString(data));//[-26, -75, -117, -24, -81, -107]
        //解码操作：byte[] -> String
        //因为Tomcat服务器是西欧程序员写的,西欧在解码的时候使用ISO-8859-1,而以UTF-8编码却以ISO-8859-1解码此时就乱码了
        String ret = new String(data, StandardCharsets.ISO_8859_1);
        System.out.println(ret);//输出：æµè¯
        // 虽然在底层此时存储的依旧是[-26, -75, -117, -24, -81, -107],但是由于字符集的不同,因此导致了通过不同解码方式看到的字符是不一样的
        // 所以所谓乱码实际上是因为以人的视角来看的，计算机是很单纯的,它并不清楚这点
        //解决方案：重新对乱码重新按照其本来的解码方式重新编码回byte[],然后重新按照UTF-8解码
        data = ret.getBytes(StandardCharsets.ISO_8859_1);
        System.out.println(Arrays.toString(data));//[-26, -75, -117, -24, -81, -107]
        ret = new String(data, StandardCharsets.UTF_8);
        System.out.println(ret);//输出：测试

        System.out.println("----------测试由于缓冲区的原因导致的乱码问题----------");
        byte[] send = "模拟的别人的信息".getBytes();
        System.out.println(Arrays.toString(send));
        byte[] buffer = new byte[11];//故意将缓冲区设小点
        String msg = new String(send, 0, buffer.length);
        System.out.println("msg.length() = " + msg.length());
        //有趣的是会发现字节序列对不上,这是因为在java底层的实现中解码失败的那部分会使用�作为填充所致,该符号的字节序列正是{-17, -65, -67}
        System.out.println(Arrays.toString(msg.getBytes()));
        System.out.println("最后实际接收到的信息 = " + msg);


    }
}
